/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.velocityme.session;

import com.velocityme.entity.Action;
import com.velocityme.entity.Anchor;
import com.velocityme.entity.ChangeDelta;
import com.velocityme.entity.ContactDetailTypes;
import com.velocityme.entity.Department;
import com.velocityme.entity.Directory;
import com.velocityme.entity.Group;
import com.velocityme.entity.GroupAccess;
import com.velocityme.entity.Issue;
import com.velocityme.entity.LicenceManagement;
import com.velocityme.entity.LoginHistory;
import com.velocityme.entity.Node;
import com.velocityme.entity.Organisation;
import com.velocityme.entity.Person;
import com.velocityme.entity.Project;
import com.velocityme.entity.Role;
import com.velocityme.entity.Sprint;
import com.velocityme.entity.State;
import com.velocityme.entity.Status;
import com.velocityme.entity.SystemNode;
import com.velocityme.entity.SystemProperties;
import com.velocityme.entity.Activity;
import com.velocityme.entity.NodeLink;
import com.velocityme.entity.TaskStateMachine;
import com.velocityme.entity.User;
import com.velocityme.entity.UserAccess;
import com.velocityme.enums.ChangeDeltaItemType;
import com.velocityme.enums.ChangeDeltaType;
import com.velocityme.enums.Permission;
import com.velocityme.utility.PermissionCache;
import com.velocityme.utility.PermissionDeniedException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

/**
 *
 * @author rcrida
 */
@Stateless
public class TreeBean implements TreeLocal {

    @PersistenceContext
    private EntityManager em;

    @EJB
    private SearchLocal m_searchLocal;

    @EJB
    private NodeLocal nodeBean;

    private Map<Class,Integer> m_nodeTypeOrder;

    private Node m_directoryNode;

    public TreeBean() {
        m_nodeTypeOrder = new HashMap<Class,Integer>();
        m_nodeTypeOrder.put(com.velocityme.entity.RootNode.class, 0);
        m_nodeTypeOrder.put(com.velocityme.entity.SystemNode.class, 1);
        m_nodeTypeOrder.put(com.velocityme.entity.SystemProperties.class, 2);
        m_nodeTypeOrder.put(com.velocityme.entity.ContactDetailTypes.class, 3);
        m_nodeTypeOrder.put(com.velocityme.entity.LicenceManagement.class, 4);
        m_nodeTypeOrder.put(com.velocityme.entity.LoginHistory.class, 5);
        m_nodeTypeOrder.put(com.velocityme.entity.Directory.class, 6);
        m_nodeTypeOrder.put(com.velocityme.entity.Node.class, 7);
        m_nodeTypeOrder.put(com.velocityme.entity.Organisation.class, 8);
        m_nodeTypeOrder.put(com.velocityme.entity.Department.class, 9);
        m_nodeTypeOrder.put(com.velocityme.entity.Person.class, 10);
        m_nodeTypeOrder.put(com.velocityme.entity.Group.class, 11);
        m_nodeTypeOrder.put(com.velocityme.entity.User.class, 12);
        m_nodeTypeOrder.put(com.velocityme.entity.State.class, 13);
        m_nodeTypeOrder.put(com.velocityme.entity.Status.class, 14);
        m_nodeTypeOrder.put(com.velocityme.entity.TaskStateMachine.class, 15);
        m_nodeTypeOrder.put(com.velocityme.entity.Role.class, 16);
        m_nodeTypeOrder.put(com.velocityme.entity.Sprint.class, 17);
        m_nodeTypeOrder.put(com.velocityme.entity.Project.class, 18);
        m_nodeTypeOrder.put(com.velocityme.entity.Anchor.class, 19);
        m_nodeTypeOrder.put(com.velocityme.entity.Action.class, 20);
        m_nodeTypeOrder.put(com.velocityme.entity.Issue.class, 21);
    }

    public void moveChildNode(KeyLocal key, Node targetNode, Node childNode) {
        if (isValidPasteNode(key, targetNode, childNode)) {
            Node currentParentNode = childNode.getParentNode();
            ChangeDelta changeDelta = childNode.addChangeDelta(ChangeDeltaType.MOVE, key.getUser());
            changeDelta.addItem(ChangeDeltaItemType.PARENT_NODE, targetNode.getPathName());
            em.persist(changeDelta);
            childNode.setParentNode(targetNode);
            em.merge(childNode);
            // invalidate the permission cache
            PermissionCache.getInstance().clear();
            // now record change in the original parent
            changeDelta = currentParentNode.addChangeDelta(ChangeDeltaType.MOVE_CHILD, key.getUser());
            changeDelta.addItem(ChangeDeltaItemType.CHILD_NODE, childNode.getName());
            changeDelta.addItem(ChangeDeltaItemType.DESTINATION_NODE, targetNode.getPathName());
            em.persist(changeDelta);
        }
        else
            throw new PermissionDeniedException();
    }

     /**
     * Checks to see if it is a legal paste node
     *
     * @param key User's security key
     * @param target The target node to which the node will be moved
     * @param child The node that is to be moved
     **/
    public boolean isValidPasteNode(KeyLocal key, Node targetNode, Node childNode) {
        // can't move system nodes
        key.assertHasPermission(childNode, Permission.NODE_EDIT);
        key.assertHasPermission(targetNode, Permission.NODE_EDIT);
        if (!(childNode instanceof SystemNode)) {
            Node parent = targetNode;
            long childNodeId = childNode.getId().longValue();
            // false if the destination is its current location
            if (targetNode.getId().longValue() == childNode.getParentNode().getId().longValue())
                return false;
            // now make sure that the target node is not a child of the node being moved
            while(parent != null) {
                if (parent.getId().longValue() == childNodeId)
                    return false;
                parent = parent.getParentNode();
            }
            return true;
        }
        return false;
    }

    public Collection<Node> getBookmarkNodes(KeyLocal key) {
        key.validateKey();
        return getSortedNodes(em.createNamedQuery("findBookmarkNodesForUser").setParameter("user", key.getUser()).getResultList());
    }


    // Add business logic below. (Right-click in editor and choose
    // "Insert Code > Add Business Method" or "Web Service > Add Operation")
 
    private Collection<Node> getSortedNodes(Collection<Node> nodes) {
        List<Node> nodeList = new ArrayList<Node>(nodes);
        // sort first into alphabectical order of name
        Collections.sort(nodeList, new Comparator<Node>() {
            public int compare(Node n1, Node n2) {
                return n1.getName().compareTo(n2.getName());
            }
        });
        // sort second into order of type
        Collections.sort(nodeList, new Comparator<Node>() {
            public int compare(Node n1, Node n2) {
                return (m_nodeTypeOrder.get(n1.getClass())).intValue() -
                       (m_nodeTypeOrder.get(n2.getClass())).intValue();
            }
        });
        return nodeList;
    }

    @Override
    public List<Node> getVisibleAncestorNodes(KeyLocal key, Node node) {
        key.validateKey();
        if (node == null) {
            return new ArrayList<Node>();
        }
        List<Node> visibleAncestorNodes = new ArrayList<Node>();
        do {
            if (!m_searchLocal.getAssignedPermissions(key, node).contains(Permission.NODE_VIEW)) {
                break;
            }
            visibleAncestorNodes.add(0, node);
        } while((node = node.getParentNode()) != null);
        return visibleAncestorNodes;
    }

    public void deleteNodeLink(KeyLocal key, Long nodeId, Long otherNodeId) {
        Node node = em.find(Node.class, nodeId);
        Node otherNode = em.find(Node.class, otherNodeId);
        NodeLink nodeLink = (NodeLink) em.createNamedQuery("findNodeLink").setParameter("node", node).setParameter("otherNode", otherNode).getSingleResult();
        key.assertHasPermission(node, Permission.NODE_EDIT);
        key.assertHasPermission(nodeLink.getFromNode(), Permission.NODE_EDIT);
        // record the delete at both ends of the link
        ChangeDelta changeDelta = node.addChangeDelta(ChangeDeltaType.DELETE_LINK, key.getUser());
        changeDelta.addItem(ChangeDeltaItemType.NODE, otherNode.getPathName());
        em.persist(changeDelta);
        changeDelta = otherNode.addChangeDelta(ChangeDeltaType.DELETE_LINK, key.getUser());
        changeDelta.addItem(ChangeDeltaItemType.NODE, node.getPathName());
        em.persist(changeDelta);
        // now delete the link
        em.remove(nodeLink);
    }

    public void createNodeLink(KeyLocal key, Long nodeId, Long otherNodeId) {
        Node node = em.find(Node.class, nodeId);
        Node otherNode = em.find(Node.class, otherNodeId);
        if (isValidLinkNode(key, node, otherNode)) {
            NodeLink link = new NodeLink(node, otherNode);
            em.persist(link);
            ChangeDelta changeDelta = node.addChangeDelta(ChangeDeltaType.CREATE_LINK, key.getUser());
            changeDelta.addItem(ChangeDeltaItemType.NODE, otherNode.getPathName());
            em.persist(changeDelta);
            // now record change in the original parent
            changeDelta = otherNode.addChangeDelta(ChangeDeltaType.CREATE_LINK, key.getUser());
            changeDelta.addItem(ChangeDeltaItemType.NODE, node.getPathName());
            em.persist(changeDelta);
        } else {
            throw new PermissionDeniedException();
        }
    }

    public boolean isValidLinkNode(KeyLocal key, Node node, Node otherNode) {
        // can't link system nodes
        if (otherNode instanceof SystemNode || node instanceof SystemNode) {
            return false;
        }
        // can't link if link already exists
        if (em.createNamedQuery("findLinkedNodes").setParameter("node", node).getResultList().contains(otherNode)) {
            return false;
        }
        key.assertHasPermission(node, Permission.NODE_EDIT);
        key.assertHasPermission(otherNode, Permission.NODE_EDIT);
        return !otherNode.equals(node);
    }

    public Collection<Node> getVisibleChildrenNodes(KeyLocal key, Node node) {
        if (node == null) {
            Collection<Node> rootNodes = new TreeSet<Node>(new Comparator<Node>() {
                public int compare(Node o1, Node o2) {
                    return o1.getId().compareTo(o2.getId());
                }
            });
            // add all nodes from user accesses
            for (UserAccess userAccess: (List<UserAccess>) em.createNamedQuery("findUserAccessByUser").
                    setParameter("user", key.getUser()).
                    getResultList()) {
                rootNodes.add(userAccess.getNode());
            }
            // add all nodes from group accesses
            for (GroupAccess groupAccess: (List<GroupAccess>) em.createNamedQuery("findGroupAccessByUser").
                    setParameter("user", key.getUser()).
                    getResultList()) {
                rootNodes.add(groupAccess.getNode());
            }
            // add all sprints for this person
            for (Sprint sprint: (List<Sprint>) em.createNamedQuery("findSprintByPersonDate").
                    setParameter("person", key.getUser().getPerson()).
                    setParameter("date", new Date()).
                    getResultList()) {
                rootNodes.add(sprint);
            }
            // now convert to list sorted by id and insert user and person at end
            rootNodes = new ArrayList<Node>(rootNodes);
            rootNodes.add(key.getUser());
            rootNodes.add(key.getUser().getPerson());
            if (m_directoryNode == null) {
                m_directoryNode = (Node) em.createNamedQuery("findDirectoryNode").getSingleResult();
            }
            rootNodes.add(m_directoryNode);
            return rootNodes;
        }
        else {
            List<Node> childrenNodes = (List<Node>)em.createNamedQuery("findOrderedChildren").
                    setParameter("node", node).
                    getResultList();
            return getSortedNodes(childrenNodes);
        }
    }

    /**
     * Gets a list of sibling nodes that can be seen by this user from the
     * specified node. If nodeId == null then the method returns null.
     *
     * @param key User's security key
     * @param node Current node for which the siblings will be found
     * @return A collection of sibling nodes that are visible from the specified node
     **/
    public Collection<Node> getVisibleSiblingNodes(KeyLocal key, Node node) {
        key.validateKey();
        Node parentNode = node.getParentNode();
        if (parentNode == null)
            return null;
        if (!m_searchLocal.getAssignedPermissions(key, parentNode).contains(Permission.NODE_VIEW))
            return null;
        Collection<Node> visibleSiblingNodes = getVisibleChildrenNodes(key, parentNode);
        visibleSiblingNodes.remove(node);
        return visibleSiblingNodes;
    }

    /**
     * Gets a list of link nodes that can be seen by this user from the
     * specified node.
     *
     * @param key User's security key
     * @param nodeId Current node in which the links will be found
     * @return A collection of nodes that are visible from the specified node
     **/
    public Collection<Node> getVisibleLinkedNodes(KeyLocal key, Node node) {
        return getSortedNodes(nodeBean.getLinkedNodes(key, node));
    }

    /**
     * Gets a list of dependency tasks that can be seen by this user from the
     * specified task.
     *
     * @param key User's security key
     * @param task Current task in which the dependencies will be found
     * @return A collection of tasks that are visible from the specified task
     **/
    public Collection<Node> getVisibleDependencyTasks(KeyLocal key, Activity task) {
        key.validateKey();
        Collection<Node> dependencyTasks = new ArrayList<Node>(em.createNamedQuery("findDependencyTasks").
                setParameter("task", task).
                getResultList());
        return getSortedNodes(dependencyTasks);
    }

    /**
     * Gets a list of dependant tasks that can be seen by this user from the
     * specified task.
     *
     * @param key User's security key
     * @param taskId Current task in which the dependants will be found
     * @return A collection of tasks that are visible from the specified task
     **/
    public Collection<Node> getVisibleDependantTasks(KeyLocal key, Activity task) {
        key.validateKey();
        Collection<Node> dependantTasks = new ArrayList<Node>(em.createNamedQuery("findDependantTasks").
                setParameter("task", task).
                getResultList());
        return getSortedNodes(dependantTasks);
    }



}
